import datetime as dt
import backtrader as bt
import matplotlib.pyplot as plt
import pandas as pd
import time

class MyStrategy(bt.Strategy):
    params = dict(maperiod=5, printlog=True)
    def __init__(self):
        self.data_close = self.datas[0].close
        self.first_buy_date = None  
        self.order = None
        self.sma1 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
        self.daily_value = []  
        self.daily_date = []   
        self.loglist=[]
        self.cash_size=0
    def next(self):
        self.daily_value.append(self.broker.getvalue())
        self.daily_date.append(self.datas[0].datetime.date(0))  
        if self.order:
            return
        indicator=round(self.data_close[0]-self.sma1[0],5)
        if not self.position:
            if  indicator> 0:  
                self.cash_size = int(0.98*self.broker.get_cash() / self.data_close[0]) # 0.98,考虑到佣金已及第二天开盘与今天收盘的差
                self.order = self.buy(size=self.cash_size)  
                if  self.first_buy_date is None:
                    self.first_buy_date = self.datas[0].datetime.date(0)
        else:
            if indicator < 0:
                self.order = self.close()
    def log(self, txt, dt=None, do_print=False):
        if self.params.printlog or do_print:
            dt = dt or self.datas[0].datetime.date(0)
            self.loglist.append('%s,%s' % (dt.isoformat(), txt))
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            return
        if order.status in [order.Completed]: 
            if order.isbuy(): 
                now_value=self.broker.get_value()
                self.log(f"buy, 价格 {order.executed.price:.2f}, 数量 {self.cash_size}, 成本 {order.executed.value:.2f}, 手续费 {order.executed.comm:.2f}, 收盘后 {now_value:.2f}")
            else:
                now_value=self.broker.get_value()
                self.log(f"sell, 价格 {order.executed.price:.2f}, 数量 {self.cash_size}, 成本 {order.executed.value:.2f}, 手续费 {order.executed.comm:.2f}, 收盘后 {now_value:.2f}")
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            pass
            #self.log("交易失败")
        self.order = None
    def notify_trade(self, trade):
        if not trade.isclosed:
            return
        self.log(f"策略收益：毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}")
    def stop(self): 
        with open('SMAlog.txt', 'w', encoding='utf-8') as f:
            f.write("")
            f.close()
        self.log(f"{self.params.maperiod}日MA均线策略, 期末总资金 {self.broker.getvalue():.2f}", do_print=True)
        if  not self.first_buy_date:
            print("回测结束，期间无买入交易")
        with open('2.1SMAlog.txt', 'a', encoding='utf-8') as f:
            for _ in self.loglist:
                f.write(f"{_}\n")             
def get_index_df(txt):
    stock_df = pd.read_csv(txt)
    stock_df = stock_df.iloc[:, :6]
    stock_df.columns = ['date', 'open', 'close', 'high', 'low', 'volume']
    stock_df.index = pd.to_datetime(stock_df['date'])
    return stock_df
def score_strategy(cagr, sharpe, calmar, max_dd):
    cagr = min(cagr, 50)
    sharpe = min(sharpe, 5)
    calmar = min(calmar, 10)
    max_dd = min(max_dd, 100)
    cagr_weight = 0.35
    sharpe_weight = 0.25
    calmar_weight = 0.25
    maxdd_weight = 0.15
    cagr_score = (cagr / 50) * 100 * cagr_weight
    sharpe_score = (sharpe / 5) * 100 * sharpe_weight
    calmar_score = (calmar / 10) * 100 * calmar_weight
    maxdd_score = ((100 - max_dd) / 100) * 100 * maxdd_weight  
    total_score = cagr_score + sharpe_score + calmar_score + maxdd_score
    return round(total_score, 2)
def run_backtest(data, maperiod,start_cash=1000000,commission=0.0002):
    cerebro = bt.Cerebro(stdstats=False)
    cerebro.broker.setcash(start_cash)
    cerebro.broker.setcommission(commission=commission)
    start_cash = cerebro.broker.get_cash()
    cerebro.adddata(data)
    cerebro.addobserver(bt.observers.Broker)
    cerebro.addobserver(bt.observers.Trades)
    cerebro.addobserver(bt.observers.BuySell) # 买卖信号，比基准与策略收益对比(真实买卖点)晚一天
    cerebro.addobserver(bt.observers.Benchmark, data=data, timeframe=bt.TimeFrame.NoTimeFrame) # 来对比基准累计收益(蓝色)与策略累计收益(红色)
    #cerebro.addobserver(bt.observers.Value) # 累计净值 bt.observers.Cash
    #cerebro.addobserver(bt.observers.DrawDown) # 每日回撤
    #cerebro.addobserver(bt.observers.TimeReturn) # 每日收益率
    #cerebro.addobserver(bt.observers.Benchmark) # 每日收益率(红色)与业绩基准收益率(蓝色)
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
    cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
    cerebro.addstrategy(MyStrategy, maperiod=maperiod)#5，10，20/30，50，60/120，200 
    t1 = time.time()
    results = cerebro.run(maxcpus=1)
    t2 = time.time()
    str_separate1=f"——————————————————{maperiod}日均线回测结果分析——————————————————"
    strategy = results[0]
    first_buy_date = strategy.first_buy_date
    first_buy_datetime= dt.datetime.combine(first_buy_date,dt.time())
    port_value = cerebro.broker.getvalue()  
    pnl = port_value - start_cash 
    str_date=f"回测时间: {first_buy_datetime} —— {end_date}"
    str_cash=f"初始总资金: {round(start_cash, 2)}\t\t期末总资金: {round(port_value, 2)}"
    str_gains=f"回测净收益: {pnl:.2f}\t回测用时:{t2-t1:.2f}s"
    print(str_separate1,'\n', str_date,'\n',str_cash,'\n',str_gains,)
    str_separate2=f"—————————————————————{maperiod}日均线策略分析———————————————————————"
    try:
        drawdown_analysis = strategy.analyzers.drawdown.get_analysis()
        sharpe_analysis = strategy.analyzers.sharpe.get_analysis()
        trades_analysis = strategy.analyzers.trades.get_analysis()
        duration_years = (end_date - first_buy_datetime).days / 365
        comu_returns=(port_value/start_cash-1)*100
        comu_cagr = ((port_value/start_cash) ** (1 / duration_years) - 1) * 100
        max_dd = drawdown_analysis.get('max', {}).get('drawdown', 0.0)
        comu_calmar = comu_cagr / max_dd if max_dd != 0 else float('inf')
        sharpe = sharpe_analysis.get('sharperatio', 'N/A')
        total_trades = trades_analysis.get('total', {}).get('total', 0)
        win_trades = trades_analysis.get('won', {}).get('total', 0)
        win_rate = 100 * win_trades / total_trades if total_trades > 0 else 0.0
        bench_start_price = index_df.loc[first_buy_datetime.strftime('%Y-%m-%d')]['close']
        bench_end_price = index_df.loc[end_date.strftime('%Y-%m-%d')]['close']
        bench_return = (bench_end_price / bench_start_price - 1) * 100
        bench_cagr = ((bench_end_price / bench_start_price) ** (1 / duration_years) - 1) * 100
        excess_cagr = comu_cagr - bench_cagr
        str_return=f"总收益率: {comu_returns:.2f}%\t\t年化收益率(CAGR): {comu_cagr:.2f}%"
        str_max_dd=f"最大回撤(max_dd): {max_dd:.2f}%"
        str_rate=f"夏普比率(sharpe): {sharpe:.2f}\t\t卡玛比率(calmar): {comu_calmar:.2f}"
        str_trades=f"交易次数: {total_trades}\t\t\t胜率: {win_rate:.2f}%"
        str_benchmark=f"基准总收益率: {bench_return:.2f}%\t\t基准年化收益率: {bench_cagr:.2f}%"
        str_excess_return=f"超额年化收益率: {excess_cagr:.2f}%" 
        score = score_strategy(comu_cagr, sharpe, comu_calmar, max_dd)
        print(str_separate2,'\n',str_return,'\n',str_benchmark,'\n',str_excess_return+'\t\t'+str_max_dd,'\n',str_rate,'\n',str_trades,)
        print(f"---{maperiod}日均线策略评分:{score}---")
    except KeyError as e:
        print(f"分析器结果提取错误: {e}")
    except Exception as e:
        print(f"分析器发生错误: {e}")
    plt.rcParams["font.sans-serif"] = ["SimHei"]
    plt.rcParams["axes.unicode_minus"] = False
    cerebro.plot(style='candle', voloverlay=False)
if __name__ == '__main__':
    csv_path='./股指/上证指数-19901219-20250609.csv'
    csv_path='./股指/纳斯达克-19910812-20250606.csv'
    start_date = dt.datetime(1991,8,12)
    end_date = dt.datetime(2025,6, 6)
    period=30#5,10,20,/30,50,60,/100,120,200
    index_df = get_index_df(csv_path)
    data = bt.feeds.PandasData(dataname=index_df,fromdate=start_date, todate=end_date)
    run_backtest(data,period)